﻿using LightCAD.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using static LightCAD.MathLib.MathUtil;
using static LightCAD.Model.VectorUtil;
using loop = LightCAD.MathLib.ListEx<LightCAD.MathLib.Vector2>;
using loop3 = LightCAD.MathLib.ListEx<LightCAD.MathLib.Vector3>;
using Color = LightCAD.MathLib.Color;
using Shape = LightCAD.Three.Shape;
namespace LightCAD.Model
{
    public class GeometryData
    {
        public ListEx<double> Position { get; set; }
        public ListEx<Int32> Index { get; set; }
        public ListEx<double> Uvs { get; set; }
        public ListEx<int> FaceUvs { get; set; }
        public Vector2 Repeat { get; set; }
        public ListEx<Vector3> TopPoints { get; set; }
        public ListEx<Vector3> BottomPoints { get; set; }
        public ListEx<Int32> HoleIndices { get; set; }
        public GeometryData Edge { get; set; }

        public ListEx<Int32> TopFaceIndex { get; set; }

        public ListEx<Int32> BottomFaceIndex { get; set; }
        public ListEx<Int32> InnerFaceIndex { get; set; }
        public ListEx<Int32> OuterFaceIndex { get; set; }

        public ListEx<Int32> curveFaceIndex { get; set; }
        public GeometryData Add(GeometryData geoData)
        {
            var offset = this.Position.Length / 3;
            this.Position.AddRange(geoData.Position);
            this.Index.AddRange(geoData.Index.Select(i => i + offset));
            this.Position.AddRange(geoData.Position);
            this.TopPoints.AddRange(geoData.TopPoints);
            this.TopFaceIndex.AddRange(geoData.TopFaceIndex.Select(i => i + offset));
            this.BottomPoints.AddRange(geoData.BottomPoints);
            this.BottomFaceIndex.AddRange(geoData.BottomFaceIndex.Select(i => i + offset));
            this.HoleIndices.AddRange(geoData.HoleIndices.Select(h => h + offset));
            this.InnerFaceIndex.AddRange(geoData.InnerFaceIndex.Select(i => i + offset));
            this.OuterFaceIndex.AddRange(geoData.OuterFaceIndex.Select(i => i + offset));
            this.curveFaceIndex.AddRange(geoData.curveFaceIndex.Select(i => i + offset));
            return this;
        }
        public BufferGeometry GetBufferGeometry()
        {
            var geo = new BufferGeometry();
            geo.attributes.position = new BufferAttribute(Position.ToArray(), 3);
            geo.setIndex(this.Index.ToArray());
            return geo;
        }
    }
    public sealed class CoordShape3
    {
        public Shape shape;
        public ListEx<loop> Points2d;
        public ListEx<ListEx<loop>> Holes2d;
        public Matrix4 coordMatrix;
        public double z;
        public ListEx<ListEx<loop3>> Holes;
        public ListEx<loop3> Points;
        public CoordShape3(Shape shape, Matrix4 coordMatrix, double z)
        {
            this.shape = shape;
            this.coordMatrix = coordMatrix;
            this.z = z;
            var subPoints = shape.getPoints();
            this.Points2d = new ListEx<loop>();
            this.Points = new ListEx<loop3>();
            this.Holes2d = new ListEx<ListEx<loop>>();
            this.Holes = new ListEx<ListEx<loop3>>();
            if (this.shape.holes?.Count > 0)
            {
                var clipper = new ClipperWrapper();
                clipper.Set(new List<List<Vector2>> { subPoints.ToList() });
                var holes = shape.holes.Select(p => p.getPoints().ToList()).ToList();
                clipper.Clips.AddRange(holes);
                clipper.Execute(ClipType.ctDifference);
                var clipperShapes = clipper.GetShapes(1e-5);
                for (int i = 0; i < clipperShapes.Count; i++)
                {
                    var cs = clipperShapes[i];
                    this.Points2d.Push(cs.Points.ToListEx());
                    this.Points.Push(cs.Points.Select(p => new Vector3(p, z).ApplyMatrix4(coordMatrix)).ToListEx());
                    this.Holes2d.Push(cs.Holes.Select(h => h.Points.ToListEx()).ToListEx());
                    this.Holes.Push(cs.Holes.Select(h => h.Points.Select(p => new Vector3(p, z).ApplyMatrix4(coordMatrix)).ToListEx()).ToListEx());
                }
            }
            else
            {
                this.Points2d.Push(subPoints);
                this.Points.Push(subPoints.Select(p => new Vector3(p, z).ApplyMatrix4(coordMatrix)).ToListEx());
                this.Holes2d.Push(new ListEx<loop>());
                this.Holes.Push(new ListEx<loop3>());
            }
        }
    }
    public static class GeoUtil
    {
        public static void SetUV(this BufferGeometry geo )
        {
            double[] uvAarry = new double[geo.attributes.position.array.Count()/3*2];
            for(var i=0;i< geo.attributes.position.array.Count()/3;i++)
            {
                var position = new Vector3().FromBufferAttribute(geo.attributes.position, i);
                var normal = new Vector3().FromBufferAttribute(geo.attributes.normal, i);
                var up = new Vector3(0,0,1);
                //TODO 
                if (Utils.Vec3EQ(normal, up,0.001)|| Utils.Vec3EQ(normal, up.Clone().Negate(), 0.001))
                {
                    up = new Vector3(0, 1, 0);
                }
                if (Utils.Vec3EQ(normal, up, 0.001) || Utils.Vec3EQ(normal, up.Clone().Negate(), 0.001))
                {
                    up = new Vector3(1, 0, 0);
                }
                var xAxi = up.Clone().Cross(normal).Normalize();
                var yAxi = normal.Clone().Cross(xAxi).Normalize();
                var mat = new Matrix4();
                mat.MakeBasis(xAxi, yAxi, normal);
                var inverseMat = new Matrix4().GetInverse(mat);
                position.ApplyMatrix4(inverseMat);
                uvAarry[i * 2] = position.X;
                uvAarry[i * 2 + 1] = position.Y;
            }
            geo.attributes.uv = new BufferAttribute(uvAarry, 2);
        }
        public static BufferGeometry ApplyTransform(this BufferGeometry geo, Transform3d trans) 
        {
            if (trans == null || trans.IsIdentity())
                return geo;
            if (trans.OnlyTranslate())
                geo.translateQuick(trans.Position);
            else
                geo.applyMatrix(trans.Matrix);
            return geo;
        }
        public static GeometryData GetStretchGeometryData(Shape shape, Matrix4 coordMat, double front, double back)
        {
            var frontShape = new CoordShape3(shape, coordMat, front);
            var backShape = new CoordShape3(shape, coordMat, back);
            return GetMappedShapeGeometryData(frontShape, backShape);
        }
        public static GeometryData GetMappedShapeGeometryData(CoordShape3 topShape, CoordShape3 bottomShape, bool hasTopFace = true, bool hasBottomFace = true)
        {
            GeometryData result = null;
            for (int pi = 0; pi < topShape.Points.Length; pi++)
            {
                var topPoints2d = topShape.Points2d[pi];
                var topPoints3d = topShape.Points[pi];
                var topHoles2d = topShape.Holes2d[pi];
                var topHoles3d = topShape.Holes[pi];

                var botPoints2d = bottomShape.Points2d[pi];
                var botPoints3d = bottomShape.Points[pi];
                var botHoles2d = bottomShape.Holes2d[pi];
                var botHoles3d = bottomShape.Holes[pi];

                TriangulateOutput output = new TriangulateOutput();
                ListEx<ListEx<int>> faces = new ListEx<ListEx<int>>();
                ListEx<int> holeIndices = new ListEx<int>();
                if (hasTopFace || hasBottomFace)
                {
                    var shape = topShape.shape;
                    faces = triangulateShape(topPoints2d, topHoles2d, output);
                }

                var topPoints = new ListEx<Vector3>().Concat(topPoints3d);
                for (var i = 0; i < topHoles3d.Length; i++)
                {
                    topPoints = topPoints.Concat(topHoles3d[i]);
                }

                var bottomPoints = new ListEx<Vector3>().Concat(botPoints3d);
                for (var i = 0; i < botHoles3d.Length; i++)
                {
                    bottomPoints = bottomPoints.Concat(botHoles3d[i]);
                }
                holeIndices = output.holeIndices;
                var data = CreateMappedGeometryData(topPoints, bottomPoints, faces, holeIndices, hasTopFace, hasBottomFace);
                data.TopPoints = topPoints;
                data.BottomPoints = bottomPoints;
                data.HoleIndices = holeIndices;
                if (result == null)
                    result = data;
                else
                    result.Add(data);
            }

            return result;
        }
        //根据两组对应的3D形状数据获得几何体数据
        public static GeometryData CreateMappedGeometryData(ListEx<Vector3> topPoints, ListEx<Vector3> bottomPoints, ListEx<ListEx<int>> faces, ListEx<int> holeIndices, bool hasTopFace, bool hasBottomFace)
        {
            var vcount = topPoints.Length;
            var idxOfs = 0;
            var posArr = new ListEx<double>();
            var indexArr = new ListEx<int>();
            //var uvsArr = new JsArr<double>();
            var topFaceIndex = new ListEx<int>();
            var bottomFaceIndex = new ListEx<int>();
            var curveFaceIndex = new ListEx<int>();
            var facePoints = new Dictionary<int, ListEx<Vector3>>();
            int face = -1;
            if (hasTopFace)
            {
                for (var i = 0; i < topPoints.Length; i++)
                {
                    posArr.Push(topPoints[i].X, topPoints[i].Y, topPoints[i].Z);
                }

                for (var i = 0; i < faces.Length; i++)
                {
                    var f = faces[i];
                    indexArr.Push(f[0] + idxOfs, f[1] + idxOfs, f[2] + idxOfs);
                    topFaceIndex.Push(f[0] + idxOfs, f[1] + idxOfs, f[2] + idxOfs);
                }
                idxOfs += vcount;

                face++;
                facePoints.Add(face, topPoints);

                //var res = generateUvs(topPoints, face);
                //var uvs = res.Item1;
                //uvsArr.AddRange(uvs);
                //var rep = res.Item2;
            }
            if (hasBottomFace)
            {
                for (var i = 0; i < bottomPoints.Length; i++)
                {
                    posArr.Push(bottomPoints[i].X, bottomPoints[i].Y, bottomPoints[i].Z);
                }
                for (var i = 0; i < faces.Length; i++)
                {
                    var f = faces[i];
                    indexArr.Push(f[0] + idxOfs, f[2] + idxOfs, f[1] + idxOfs);//这里要换方向
                    bottomFaceIndex.Push(f[0] + idxOfs, f[2] + idxOfs, f[1] + idxOfs);
                }
                idxOfs += vcount;

                //face++;
                //var res = generateUvs(bottomPoints, face);
                //var uvs = res.Item1;
                //uvsArr.AddRange(uvs);
                //var rep = res.Item2;

                face++;
                facePoints.Add(face, bottomPoints);

            }
            Vector3 pbtmNext;
            Vector3 ptopNext;
            var len = vcount;
            if (holeIndices.Length > 0)
                len = holeIndices[0];//如果有洞，外边到第一个洞的起始

            var idxCount = 0;
            for (var i = 0; i < len; i++)
            {
                var vecs = new ListEx<Vector3>();
                var pbtm = bottomPoints[i];
                var ptop = topPoints[i];
                if (i == len - 1)
                {
                    pbtmNext = bottomPoints[0];
                    ptopNext = topPoints[0];
                }
                else
                {
                    pbtmNext = bottomPoints[i + 1];
                    ptopNext = topPoints[i + 1];
                }
                //if (pbtm == null || pbtmNext == null || ptopNext == null)
                //    continue;

                posArr.Push(pbtm.X, pbtm.Y, pbtm.Z);
                posArr.Push(pbtmNext.X, pbtmNext.Y, pbtmNext.Z);
                posArr.Push(ptopNext.X, ptopNext.Y, ptopNext.Z);
                posArr.Push(ptop.X, ptop.Y, ptop.Z);

                //face++;
                vecs.Push(pbtm, pbtmNext, ptopNext, ptop);
                //var res = generateUvs(vecs, face);
                //var uvs = res.Item1;
                //uvsArr.AddRange(uvs);
                //var rep = res.Item2;

                face++;
                facePoints.Add(face, vecs);

                indexArr.Push(i * 4 + idxOfs, i * 4 + 1 + idxOfs, i * 4 + 2 + idxOfs);
                indexArr.Push(i * 4 + idxOfs, i * 4 + 2 + idxOfs, i * 4 + 3 + idxOfs);
                curveFaceIndex.Push(i * 4 + idxOfs, i * 4 + 1 + idxOfs, i * 4 + 2 + idxOfs);
                curveFaceIndex.Push(i * 4 + idxOfs, i * 4 + 2 + idxOfs, i * 4 + 3 + idxOfs);
                idxCount += 4;
            }

            if (holeIndices.Length > 0)
            {
                idxOfs += idxCount;
                for (var i = 0; i < holeIndices.Length; i++)
                {
                    var end = vcount;
                    if (i + 1 < holeIndices.Length)
                    {
                        end = holeIndices[i + 1];
                    }
                    var start = holeIndices[i];
                    idxCount = 0;
                    for (var j = start; j < end; j++)
                    {
                        var vecs = new ListEx<Vector3>();
                        var pbtm = bottomPoints[j];
                        var ptop = topPoints[j];
                        if (j == end - 1)
                        {
                            pbtmNext = bottomPoints[start];
                            ptopNext = topPoints[start];
                        }
                        else
                        {
                            pbtmNext = bottomPoints[j + 1];
                            ptopNext = topPoints[j + 1];
                        }
                        posArr.Push(pbtm.X, pbtm.Y, pbtm.Z);
                        posArr.Push(pbtmNext.X, pbtmNext.Y, pbtmNext.Z);
                        posArr.Push(ptopNext.X, ptopNext.Y, ptopNext.Z);
                        posArr.Push(ptop.X, ptop.Y, ptop.Z);

                        //face++;
                        vecs.Push(pbtm, pbtmNext, ptopNext, ptop);
                        //var res = generateUvs(vecs, face);
                        //var uvs = res.Item1;
                        //uvsArr.AddRange(uvs);
                        //var rep = res.Item2;
                        face++;
                        facePoints.Add(face, vecs);

                        indexArr.Push(idxCount + idxOfs, idxCount + 1 + idxOfs, idxCount + 2 + idxOfs);//这里方向已经朝外了
                        indexArr.Push(idxCount + idxOfs, idxCount + 2 + idxOfs, idxCount + 3 + idxOfs);
                        curveFaceIndex.Push(idxCount + idxOfs, idxCount + 1 + idxOfs, idxCount + 2 + idxOfs);//这里方向已经朝外了
                        curveFaceIndex.Push(idxCount + idxOfs, idxCount + 2 + idxOfs, idxCount + 3 + idxOfs);
                        idxCount += 4;
                    }
                    idxOfs += idxCount;
                }
            }

            //var res = generateUvs(facePoints);
            //var uvsArr = res.Item1;
            //var faceUvs = res.Item2;

            var data = new GeometryData();
            data.Position = posArr;
            data.Index = indexArr;
            //data.Uvs = uvsArr;
            //data.FaceUvs = faceUvs;
            data.TopFaceIndex = topFaceIndex;
            data.BottomFaceIndex = bottomFaceIndex;
            data.InnerFaceIndex = curveFaceIndex.Take(curveFaceIndex.Count / 2 - 9).ToListEx();
            data.curveFaceIndex = curveFaceIndex;
            try
            {
                data.OuterFaceIndex = curveFaceIndex.Clone().Splice(curveFaceIndex.Count / 2 - 3, data.InnerFaceIndex.Count);
            }
            catch (Exception)
            {

            }
            return data;
        }
        public static ListEx<ListEx<int>> triangulateShape(ListEx<Vector2> contour, ListEx<ListEx<Vector2>> holes, TriangulateOutput output)
        {
            var faceIndexs = ShapeUtils.triangulateShape(contour, holes, output);
            var vertices = new ListEx<Vector2>();
            vertices.Push(contour.ToArray());
            holes.ForEach(h => vertices.Push(h.ToArray()));
            for (int i = 0; i < faceIndexs.Length; i++)
            {
                var faceIndex = faceIndexs[i];
                if (ShapeUtils.isClockWise(GetIndexPoints(vertices, faceIndex)))
                {
                    var temp = faceIndex[0];
                    faceIndex[0] = faceIndex[2];
                    faceIndex[2] = temp;
                }
            }
            return faceIndexs;
        }

        public static Box3 ComputeWorldBBox(Object3D obj, Box3 box = null)
        {
            box = box ?? new Box3();
            if (obj is IGeometry)
            {
                var geo = (obj as IGeometry).getGeometry();
                geo.computeBoundingBox();
                box.Union(geo.boundingBox.Clone().ApplyMatrix4(obj.matrixWorld));
            }
            else
                for (int i = 0; i < obj.children.Length; i++)
                {
                    ComputeWorldBBox(obj.children[i], box);
                }
            return box;
        }
        /// <summary>
        /// 必须保证平面和三角形有交线
        /// </summary>
        /// <param name="plane"></param>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="c"></param>
        /// <returns></returns>
        public static ListEx<ListEx<Vector3>> PlaneSplitTriangle(Plane plane, Vector3 a, Vector3 b, Vector3 c, JsObj<object> ext = null, double? eps = null)
        {
            //如果三角片被平面分割，那么分割线就是三角片在平面正面部分的底部交线
            var result = new ListEx<ListEx<Vector3>>();
            result.Push(null, null);
            var front = new ListEx<Vector3>();
            var back = new ListEx<Vector3>();
            var dis_a = plane.DistanceToPoint(a);
            var dis_b = plane.DistanceToPoint(b);
            var dis_c = plane.DistanceToPoint(c);
            if (Math.Abs(dis_a + dis_b + dis_c) < 1)
            {

            }
            var _eps = 0.0001;
            if (eps != null)
                _eps = eps.Value;
            else
                eps = _eps;

            //共面，返回空
            if (NumEQ(dis_a, 0, _eps) && NumEQ(dis_b, 0, _eps) && NumEQ(dis_c, 0, _eps))
            {
                if (ext != null) ext["type"] = "coin";
                front.Push(a, b, c);
                result[0] = front;
                if (ext != null)
                {
                    var lines = new ListEx<Vector3>();
                    lines.Push(a, b);
                    lines.Push(b, c);
                    ext["line"] = lines;
                }
                return result;
            }
            var disArr = new List<double> { dis_a, dis_b, dis_c };
            //在当前多边形前面，全部参与遮盖
            if (FloatGE(dis_a, 0, _eps) && FloatGE(dis_b, 0, _eps) && FloatGE(dis_c, 0, _eps) && disArr.FindAll(d => d > _eps).Count >= 1)
            {
                if (ext != null) ext["type"] = "front";
                front.Push(a, b, c);
                result[0] = front;

                var lines = new ListEx<Vector3>();
                if (NumEQ(dis_a, 0, eps) && NumEQ(dis_b, 0, eps))
                    lines.Push(a, b);
                if (NumEQ(dis_a, 0, eps) && NumEQ(dis_c, 0, eps))
                    lines.Push(a, c);
                if (NumEQ(dis_b, 0, eps) && NumEQ(dis_c, 0, eps))
                    lines.Push(b, c);
                if (lines.Length > 0)
                    ext["line"] = lines;
                return result;
            }
            //在当前多边形后面，没有遮盖
            else if (FloatLE(dis_a, 0, _eps) && FloatLE(dis_b, 0, _eps) && FloatLE(dis_c, 0, _eps) && disArr.FindAll(d => d < -_eps).Count >= 1)
            {
                if (ext != null) ext["type"] = "back";
                back.Push(a, b, c);
                result[1] = back;

                var lines = new ListEx<Vector3>();
                if (NumEQ(dis_a, 0, eps) && NumEQ(dis_b, 0, eps))
                    lines.Push(a, b);
                if (NumEQ(dis_a, 0, eps) && NumEQ(dis_c, 0, eps))
                    lines.Push(a, c);
                if (NumEQ(dis_b, 0, eps) && NumEQ(dis_c, 0, eps))
                    lines.Push(b, c);
                if (lines.Length > 0)
                    ext["line"] = lines;
                return result;
            }

            var ins_ab = plane.IntersectLine(new Line3(a, b), new Vector3());
            var ins_bc = plane.IntersectLine(new Line3(b, c), new Vector3());
            var ins_ca = plane.IntersectLine(new Line3(c, a), new Vector3());
            eps = _eps;
            if (ext != null) ext["type"] = "split";
            ListEx<Vector3> splitLine = null;
            if (ext != null)
            {
                splitLine = new ListEx<Vector3>();
                ext["line"] = splitLine;
            }
            if (NumEQ(dis_a, 0, eps))
            {

                if (dis_b > 0)
                {
                    front.Push(a, b, ins_bc);
                    back.Push(c, a, ins_bc);
                    splitLine.Push(ins_bc, a);
                }
                else
                {
                    front.Push(c, a, ins_bc);
                    back.Push(a, b, ins_bc);
                    splitLine.Push(a, ins_bc);
                }

            }
            else if (NumEQ(dis_b, 0, eps))
            {
                if (dis_a > 0)
                {
                    front.Push(a, b, ins_ca);
                    back.Push(b, c, ins_ca);
                    splitLine.Push(ins_ca, b);
                }
                else
                {
                    front.Push(b, c, ins_ca);
                    back.Push(a, b, ins_ca);
                    splitLine.Push(b, ins_ca);
                }
            }
            else if (NumEQ(dis_c, 0, eps))
            {
                if (dis_a > 0)
                {
                    front.Push(c, a, ins_ab);
                    back.Push(b, c, ins_ab);
                    splitLine.Push(ins_ab, c);
                }
                else
                {
                    front.Push(b, c, ins_ab);
                    back.Push(c, a, ins_ab);
                    splitLine.Push(c, ins_ab);
                }
            }
            else if (dis_a > 0 && dis_b > 0)
            {
                front.Push(a, b, ins_bc, ins_ca);
                back.Push(c, ins_ca, ins_bc);
                splitLine.Push(ins_bc, ins_ca);
            }
            else if (dis_a < 0 && dis_b < 0)
            {
                front.Push(c, ins_ca, ins_bc);
                back.Push(a, b, ins_bc, ins_ca);
                splitLine.Push(ins_ca, ins_bc);
            }
            else if (dis_b > 0 && dis_c > 0)
            {
                front.Push(b, c, ins_ca, ins_ab);
                back.Push(a, ins_ab, ins_ca);
                splitLine.Push(ins_ca, ins_ab);
            }
            else if (dis_b < 0 && dis_c < 0)
            {
                front.Push(a, ins_ab, ins_ca);
                back.Push(b, c, ins_ca, ins_ab);
                splitLine.Push(ins_ab, ins_ca);
            }
            else if (dis_c > 0 && dis_a > 0)
            {
                front.Push(c, a, ins_ab, ins_bc);
                back.Push(b, ins_bc, ins_ab);
                splitLine.Push(ins_ab, ins_bc);
            }
            else if (dis_c < 0 && dis_a < 0)
            {
                front.Push(b, ins_bc, ins_ab);
                back.Push(c, a, ins_ab, ins_bc);
                splitLine.Push(ins_bc, ins_ab);
            }
            if (front.Length == 0) front = null;
            if (back.Length == 0) back = null;
            result[0] = front;
            result[1] = back;
            return result;

        }

        public static BufferGeometry GeoApplyMatrix3d(BufferGeometry geo, Matrix3 m3d)
        {
            var posArr = geo.attributes.position.array;
            for (int i = 0; i < posArr.Length; i += 3)
            {
                Vector2 v2 = new Vector2(posArr[i], posArr[i + 1]);
                v2 = m3d.MultiplyPoint(v2);
                posArr[i] = v2.X;
                posArr[i + 1] = v2.Y;
            }
            return geo;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static ListEx<Vector2> GetIndexPoints(IEnumerable<Vector2> contour, IList<int> indexArr)
        {
            return indexArr.Select(i => contour.ElementAt(i)).ToListEx();
        }

        public static ListEx<Triangle> GetTriangles(this BufferGeometry geo)
        {
            var idxArr = geo.index.intArray;
            var posArr = geo.attributes.position.array;
            var ts = new ListEx<Triangle>(idxArr.Length / 3);
            for (int i = 0; i < idxArr.Length; i += 3)
            {
                var a = new Vector3(posArr[idxArr[i] * 3], posArr[idxArr[i] * 3 + 1], posArr[idxArr[i] * 3 + 2]);
                var b = new Vector3(posArr[idxArr[i + 1] * 3], posArr[idxArr[i + 1] * 3 + 1], posArr[idxArr[i + 1] * 3 + 2]);
                var c = new Vector3(posArr[idxArr[i + 2] * 3], posArr[idxArr[i + 2] * 3 + 1], posArr[idxArr[i + 2] * 3 + 2]);
                var triangle = new Triangle(a, b, c);
                ts[i / 3] = triangle;
            }
            return ts;
        }
        public static ListEx<CoordShape3> GetSectionMeshShapes(Plane plane, Mesh mesh)
        {
            var geo = mesh.geometry;
            var mat4 = mesh.matrixWorld;
            var box = geo.boundingBox.Clone().ApplyMatrix4(mat4);
            if (box.IntersectsPlane(plane))
            {
                var tris = geo.GetTriangles();
                var lines = new ListEx<ListEx<Vector3>>();
                for (var i = 0; i < tris.Length; i++)
                {
                    var tri = tris[i];
                    var ext = new JsObj<object>();
                    var a = tri.A.ApplyMatrix4(mat4);
                    var b = tri.B.ApplyMatrix4(mat4);
                    var c = tri.C.ApplyMatrix4(mat4);
                    ext["eps"] = 0.01;
                    var polys = PlaneSplitTriangle(plane, a, b, c, ext, 0.001);
                    var ext_type = ext["type"].ToString();
                    var ext_line = ext["line"] as ListEx<Vector3>;
                    if (ext_type != "coin" && ext_line != null && ext_line.Length > 0)
                    {
                        lines.Push(ext_line);
                    }
                    else
                    {

                    }
                }

                if (lines.Length > 0)//剖到mesh，找截面
                {
                    var loops = FindLoops(lines, plane);
                    return loops;
                }
            }
            return null;
        }
        private static ListEx<CoordShape3> FindLoops(ListEx<ListEx<Vector3>> splitLines, Plane clipPlane)
        {
            splitLines.RemoveAll(sp => Vec3EQ(sp[0], sp[1], 1e-4));
            if (splitLines.Count < 3)
                return null;
            double maxErrVal = 1e-3;
            var triPlane = clipPlane.Clone().Negate();
            var dot = clipPlane.Normal.Dot(new Vector3(0, 1, 0));
            Matrix4 coordMat = new Matrix4();
            if (FloatEQ(dot, 0))//垂直剖面
            {
                var yDir = new Vector3(0, 1, 0);
                coordMat.MakeBasis(yDir.Clone().Cross(triPlane.Normal).Normalize(), yDir, triPlane.Normal)
                    .SetPosition(splitLines[0][1]);
            }
            else if (FloatEQ(1, Math.Abs(dot))) //水平剖面
            {
                var xDir = new Vector3(1, 0, 0);
                coordMat.MakeBasis(xDir, triPlane.Normal.Clone().Cross(xDir).Normalize(), triPlane.Normal)
                   .SetPosition(splitLines[0][1]);
            }
            else
            {
                var xDir = splitLines[0][1].Clone().Sub(splitLines[0][0]).Normalize();
                coordMat.MakeBasis(xDir, triPlane.Normal.Clone().Cross(xDir).Normalize(), triPlane.Normal)
                  .SetPosition(splitLines[0][1]);
            }
            var inverseMat = coordMat.Clone().Invert();
            var edges = splitLines.Select(l => new LineCurve(l[0].ApplyMatrix4(inverseMat).ToVector2(), l[1].ApplyMatrix4(inverseMat).ToVector2())).ToListEx();
            edges.ForEach(e => e.getLength());
            //AlbDrawForm form = new AlbDrawForm(edges.ToJsArr());
            //form.ShowDialog();
            var min = double.MaxValue;

            for (int i = 0; i < edges.Count;)
            {
                var edge = edges[i];
                if (edge.cacheLength < maxErrVal)
                {
                    edges.RemoveAt(i);
                    continue;
                }
                if (min > edge.cacheLength)
                    min = edge.cacheLength;
                i++;
            }
            if (edges.Count < 3)
                return null;
            var errVal = min * 0.9999;
            errVal = Math.Min(errVal, maxErrVal);

            List<LineCurve> loop = new List<LineCurve>();
            LineCurve currLine = edges[0];
            var loops = new List<List<LineCurve>>();
            loop.Add(currLine);
            edges.RemoveAt(0);

            var currEnd = currLine.v2;
            var currStart = currLine.v1;
            var endBreak = false;
            var startBreak = false;
            //平面切三角片 的分割线方向已经是有序的了，所以不再需要End找End的情况
            while (edges.Count > 0)
            {
                int joinEndIndex = -1, joinStartIndex = -1;
                if (!endBreak)
                    joinEndIndex = edges.FindIndex(e =>
                    {
                        if (Vec2EQ(e.v1, currEnd, errVal))
                            return true;
                        return false;
                    });

                bool endLoop = false;
                if (joinEndIndex > -1)
                {
                    currLine = edges[joinEndIndex];
                    currEnd = currLine.v2;
                    edges.RemoveAt(joinEndIndex);
                    if (IsParallel(currLine.direction, loop.Last().direction, 1e-4))
                    {
                        var lastLoop = loop.Last();
                        if (Vec2EQ(currLine.direction, lastLoop.direction, 1e-4))
                        {
                            lastLoop.v2.Copy(currEnd);
                            lastLoop.getLength();
                        }
                        else
                        {
                            //反向
                            //长度比现在短
                            if (lastLoop.cacheLength < currLine.cacheLength)
                            {
                                if (loop.Count == 1)
                                {
                                    lastLoop.v1.Copy(currLine.v1);
                                    lastLoop.v2.Copy(currEnd);
                                    lastLoop.getLength();
                                    currStart = lastLoop.v1;
                                    currEnd = currLine.v2;
                                }
                            }
                            else currEnd = lastLoop.v2;
                        }

                    }
                    else
                        loop.Add(currLine);
                    if (Vec2EQ(currEnd, currStart, errVal))
                        endLoop = true;
                }
                else
                    endBreak = true;
                //startBreak = true;//开始点向前查找关闭  
                if (!startBreak && !endLoop)
                    joinStartIndex = edges.FindIndex(e =>
                    {
                        if (Vec2EQ(e.v2, currStart, errVal))
                            return true;
                        return false;
                    });
                if (joinStartIndex > -1)
                {
                    currLine = edges[joinStartIndex];
                    currStart = currLine.v1;
                    edges.RemoveAt(joinStartIndex);
                    if (IsParallel(currLine.direction, loop[0].direction, 1e-4))
                    {
                        if (Vec2EQ(currLine.direction, loop[0].direction, 1e-4))
                        {
                            loop[0].v1.Copy(currStart);
                            loop[0].getLength();
                        }
                        else
                        {
                            if (loop[0].cacheLength < currLine.cacheLength)
                            {
                                if (loop.Count == 1)
                                {
                                    currStart = currLine.v1;
                                    currEnd = currLine.v2;
                                    loop[0].v1.Copy(currLine.v1);
                                    loop[0].v2.Copy(currLine.v2);
                                    loop[0].getLength();
                                }
                            }
                            else
                                currStart = loop[0].v1;
                        }
                    }
                    else
                        loop.Insert(0, currLine);
                    if (Vec2EQ(currStart, currEnd, errVal))
                        endLoop = true;
                }
                else
                    startBreak = true;
                if (endLoop || (startBreak && endBreak) || edges.Count == 0)//如果闭环或者前后路都断了
                {
                    loops.Add(loop);
                    //AlbDrawForm form1 = new AlbDrawForm(loop.ToJsArr());
                    //form1.ShowDialog();
                    if (edges.Count > 0)
                    {
                        startBreak = false;
                        endBreak = false;
                        loop = new List<LineCurve>();
                        currLine = edges[0];
                        loop.Add(currLine);
                        edges.RemoveAt(0);
                        currEnd = currLine.v2;
                        currStart = currLine.v1;
                    }
                }
            }
            var shapes = loops.Select(l =>
            {
                var shape = LoopToShape(l, true);
                return shape;
            }
            ).ToListEx();

            var loopHoles = new Dictionary<Shape, List<Shape>>();
            for (int i = 0; i < shapes.Length; i++)
            {
                var shape = shapes[i];
                var parentShape = shapes.Find((Predicate<Shape>)(s => s != shape && !s.ext.ContainsKey("IsHole") && s.ContainShape(shape)));
                if (parentShape != null)
                {
                    if (!loopHoles.ContainsKey(parentShape))
                        loopHoles[parentShape] = new List<Shape>();
                    loopHoles[parentShape].Add(shape);
                    shape.ext["IsHole"] = true;
                }
                else
                {
                    if (!loopHoles.ContainsKey(shape))
                        loopHoles[shape] = new List<Shape>();
                }
            }
            var result = new ListEx<CoordShape3>();
            foreach (var item in loopHoles)
            {
                var outer = item.Key;
                var holes = item.Value;
                outer.holes.Push(holes.ToArray());
                CoordShape3 shape3 = new CoordShape3(outer, coordMat, 0);
                result.Push(shape3);
            }
            return result;
        }
        private static ListEx<Triangle> TriLoops(Dictionary<Shape, List<Shape>> shapeHoles, Matrix4 coordMat4)
        {
            var result = new ListEx<Triangle>();
            foreach (var item in shapeHoles)
            {

                var loop = item.Key;
                var holeloops = item.Value;
                var _loop = loop.getPoints();
                var holes = holeloops.Select(s => s.getPoints()).ToListEx();
                var points = new ListEx<Vector2>();
                points.Push(_loop.ToArray());
                holes.ForEach(h => points.Push(h.ToArray()));
                var output = new TriangulateOutput();
                //var str = AlbGeoUtil.getString(_loop);
                var faces = triangulateShape(_loop, holes, output);
                if (faces == null) continue;
                for (var k = 0; k < faces.Length; k++)
                {
                    var f = faces[k];
                    var f0 = f[0];
                    var f1 = f[1];
                    var f2 = f[2];
                    var tri = new Triangle(v2tov3(points[f0]), v2tov3(points[f1]), v2tov3(points[f2]));
                    result.Push(tri);
                }
                Vector3 v2tov3(Vector2 v2)
                {
                    return new Vector3(v2).ApplyMatrix4(coordMat4);
                }
            }
            return result;
        }
        private static Shape LoopToShape(List<LineCurve> loop, bool mustCCW = false)
        {
            try
            {
                var points = loop.Select(l =>
                    l.v1
                ).ToListEx();
                if (!Vec2EQ(loop[0].v1, loop.Last().v2))
                    points.Add(loop.Last().v2);
                if (mustCCW && ShapeUtils.isClockWise(points))
                    points.Reverse();
                var shape = new Shape(points);
                return shape;
            }
            catch (Exception e)
            {

                throw e;
            }

        }

    }
    public static class ShapeUtil
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Shape Rect(double minx, double miny, double maxx, double maxy)
        {
            return new Shape(new ListEx<Vector2> {
                new Vector2(minx,miny),
                new Vector2(maxx,miny),
                new Vector2(maxx,maxy),
                new Vector2(minx,maxy),
            });
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Shape Circle(double cx, double cy, double radius)
        {
            var shape = new Shape();
            shape.arc(cx, cy, radius, 0, Math.PI * 2, false);
            return shape;
        }
        public static Shape Hexagon(double cx, double cy, double radius)
        {
            var circle = new ArcCurve(cx, cy, radius, 0, Math.PI * 2, false);
            return new Shape(circle.getPoints(6));
        }
        /// <summary>
        /// 多边形向内或向外等距离扩展算法 PS:已考虑凹凸性
        /// </summary>
        /// <param name="shapePoints"></param>
        /// <param name="thickNess"></param>
        /// <param name="isClose"></param>
        /// <returns></returns>
        public static ListEx<Vector2> ComputeRingPoints(ListEx<Vector2> shapePoints, double thickNess, bool isClose = true)
        {
            if (shapePoints == null || shapePoints.Count < 1)
                return shapePoints;
            Vector2 v1, v2, v;
            double sin0;
            var newVertices = new ListEx<Vector2>();
            if (shapePoints[0].Equals(shapePoints[shapePoints.Length - 1])) shapePoints.Pop();
            var psLen = shapePoints.Length;
            //若顺时针则改逆时针 根据具体情况决定是否在外面改成顺时针做空洞
            if (!ShapeUtils.isClockWise(shapePoints)) shapePoints.Reverse();
            if (isClose)
            {
                for (var i = 0; i < psLen; i++)
                {

                    var currCurve = new LineCurve() { v1 = shapePoints[i], v2 = shapePoints[(i + 1) % psLen] };
                    while (MathUtil.NumEQ(currCurve.getLength(), 0))
                    {
                        i++;
                        if (i == psLen) break;
                        currCurve = new LineCurve() { v1 = shapePoints[i], v2 = shapePoints[(i + 1) % psLen] };
                    }

                    var nextCurve = new LineCurve() { v1 = shapePoints[(i + 1) % psLen], v2 = shapePoints[(i + 2) % psLen] };
                    while (MathUtil.NumEQ(nextCurve.getLength(), 0))
                    {
                        i++;
                        if (i == psLen) break;
                        nextCurve = new LineCurve() { v1 = shapePoints[(i + 1) % psLen], v2 = shapePoints[(i + 2) % psLen] };
                    }
                    //当前线段向量
                    v1 = new Vector2(0, 0);
                    //下一条线段反向量
                    v2 = new Vector2(0, 0);
                    //多边形顶点扩展方向
                    v = new Vector2(0, 0);
                    v1.SubVectors(currCurve.v2, currCurve.v1);
                    v2.SubVectors(nextCurve.v1, nextCurve.v2);
                    v1.Normalize();
                    v2.Normalize();

                    sin0 = Math.Sqrt(Math.Pow(v1.Clone().Cross(v2), 2));
                    if (sin0 < 0.0001)
                    {
                        continue;
                    }
                    //v1,v2长度是厚度/sin0
                    v.AddVectors(v1.SetLength((double)(thickNess / sin0)), v2.SetLength((double)(thickNess / sin0)));
                    v2.Negate();//变回下一线段正向量
                                //判断凹还是凸
                    if (v1.Cross(v2) < 0)
                        v.Negate();//凹角 则扩展方向取反
                    v.Add(currCurve.v2);
                    if (i == psLen - 1)
                        newVertices.Unshift(v);//最后处理起点，放到最前面
                    else newVertices.Push(v);
                }
            }
            else
            {//不是多边形只是多线段起点终点不入循环单独处理
                var beginDir = shapePoints[1].Clone().Sub(shapePoints[0]).Normalize();
                var beginMoveDir = beginDir.RotateAround(new Vector2(0, 0), -(double)Math.PI / 2);
                var newBegin = shapePoints[0].Add(beginMoveDir.Clone().MultiplyScalar(thickNess));
                newVertices.Push(newBegin);
                for (var i = 0; i < psLen - 2; i++)
                {
                    var currCurve = new LineCurve() { v1 = shapePoints[i], v2 = shapePoints[(i + 1) % psLen] };

                    var nextCurve = new LineCurve() { v1 = shapePoints[(i + 1) % psLen], v2 = shapePoints[(i + 2) % psLen] };
                    //当前线段向量
                    v1 = new Vector2(0, 0);
                    //下一条线段反向量
                    v2 = new Vector2(0, 0);
                    //多边形顶点扩展方向
                    v = new Vector2(0, 0);
                    v1.SubVectors(currCurve.v2, currCurve.v1);
                    v2.SubVectors(nextCurve.v1, nextCurve.v2);
                    v1.Normalize();
                    v2.Normalize();

                    sin0 = Math.Sqrt(Math.Pow(v1.Cross(v2), 2));
                    //v1,v2长度是厚度/sin0
                    v.AddVectors(v1.SetLength((double)(thickNess / sin0)), v2.SetLength((double)(thickNess / sin0)));
                    v2.Negate();//变回下一线段正向量
                                //判断凹还是凸
                    if (v1.Cross(v2) < 0) v.Negate();//凹角 则扩展方向取反
                    v.Add(currCurve.v2);
                    newVertices.Push(v);
                }
                var endDir = shapePoints[psLen - 1].Clone().Sub(shapePoints[psLen - 2]).Normalize();
                var endMoveDir = beginDir.RotateAround(new Vector2(0, 0), -(double)Math.PI / 2);
                var newEnd = shapePoints[0].Add(beginMoveDir.Clone().MultiplyScalar(thickNess));
                newVertices.Push(newEnd);
            }
            //AlbGeoUtil.Test = true;
            //AlbGeoUtil.TestPolyToArc(ele.Ref.ModelSpace.scene, new AlbPoly(shapePoints), new Matrix4());
            //AlbGeoUtil.TestPolyToArc(ele.Ref.ModelSpace.scene, new AlbPoly(newVertices), new Matrix4());
            return newVertices;

        }

        public static bool ContainShape(this Shape host, Shape shape)
        {
            var thisBox = new Box2().SetFromPoints(host.getPoints().ToArray());
            var box = new Box2().SetFromPoints(shape.getPoints().ToArray());
            if (!thisBox.ContainsBox(box))
            {
                return false;
            }

            if (!IntersectShape(host, shape, out ListEx<Shape> interShapes))
            {
                return false;
            }

            double x = interShapes.Select(s => s.OutArea()).ToList().Aggregate((double a, double b) => a + b);
            return FloatEQ(x, shape.OutArea(), 1.0);
        }
        public static double OutArea(this Shape shape)
        {
            return Math.Abs(ShapeUtils.area(shape.getPoints()));
        }
        public static bool IntersectShape(this Shape sub, Shape shape, double areaErrVal = 100.0)
        {
            ListEx<Shape> interShapes;
            return sub.IntersectShape(shape, out interShapes, areaErrVal);
        }

        public static bool IntersectShape(this Shape host, Shape shape, out ListEx<Shape> interShapes, double areaErrVal = 100.0)
        {
            var thisBox = new Box2().SetFromPoints(host.getPoints().ToArray());
            var box = new Box2().SetFromPoints(shape.getPoints().ToArray());
            interShapes = null;
            if (thisBox.IntersectsBox(box))
            {
                var clipper = new ClipperWrapper();
                clipper.Set(new List<List<Vector2>>
                {
                    host.getPoints().ToList()
                });
                clipper.SetClips(new List<List<Vector2>> { shape.getPoints() });
                clipper.Execute(ClipType.ctIntersection);
                interShapes = clipper.GetShapes(0.01).Select(s => toShape(s)).ToListEx();
                Shape toShape(ClipperShape cs)
                {
                    var result = new Shape(cs.Points.ToListEx());
                    result.holes.Push(cs.Holes.Select(csh => toShape(csh) as Three.Path).ToArray());
                    return result;
                }
                if (interShapes.Count == 0)
                {
                    return false;
                }

                interShapes.RemoveAll(s => s.OutArea() < areaErrVal);
                return interShapes.Count > 0;
            }

            return false;
        }

    }
    public static class CurveUtil
    {
        /// <summary>
        /// 线线相交
        /// </summary>
        /// <param name=""></param>
        /// <param name=""></param>
        /// <param name=""></param>
        /// <param name=""></param>
        /// <returns></returns>
        public static Vector2 LineIntersectLine2D(Vector2 p1, Vector2 dir1, Vector2 p2, Vector2 dir2, double eps = MathUtil.EPS)
        {
            if (MathUtil.NumEQ(dir1.Clone().Cross(dir2), 0, eps))
                return null;//共线
            if (MathUtil.NumEQ(dir1.Y, 0, eps) && MathUtil.NumEQ(dir2.X, 0, eps))
            {//两线正交垂直
                return new Vector2(p2.X, p1.Y);
            }
            else if (MathUtil.NumEQ(dir1.X, 0, eps) && MathUtil.NumEQ(dir2.Y, 0, eps))
            {//两线正交垂直
                return new Vector2(p1.X, p2.Y);
            }
            double a1 = dir1.Y, b1 = -dir1.X, c1 = p1.Y * dir1.X - p1.X * dir1.Y;
            double a2 = dir2.Y, b2 = -dir2.X, c2 = p2.Y * dir2.X - p2.X * dir2.Y;
            var d = a1 * b2 - a2 * b1;
            var x = (b1 * c2 - b2 * c1) / d;
            var y = (c1 * a2 - c2 * a1) / d;
            return new Vector2(x, y);
        }
        /// <summary>
        /// 线段与x垂直线相交
        /// </summary>
        /// <param name=""></param>
        /// <param name=""></param>
        /// <param name=""></param>
        /// <param name=""></param>
        /// <returns></returns>
        public static Vector2[] LineIntersectX2D(Vector2 p1, Vector2 p2, double x, out string ext_Type, bool isExt = true)
        {
            ext_Type = "";
            List<Vector2> points = new List<Vector2>();
            if (MathUtil.NumEQ(p1.X, x) && MathUtil.NumEQ(p2.X, x))
            {//线与x重合
                if (isExt)
                    ext_Type = "coin";
                return new Vector2[] { p1, p2 };
            }
            if (MathUtil.NumEQ(p1.X, x))
            {//p1与x重合
                if (isExt)
                    ext_Type = "end1";
                return new Vector2[] { p1 };
            }
            if (MathUtil.NumEQ(p2.X, x))
            {//p2与x重合
                if (isExt)
                    ext_Type = "end2";
                return new Vector2[] { p2 };
            }

            var minx = p1.X < p2.X ? p1.X : p2.X;
            var maxx = p1.X > p2.X ? p1.X : p2.X;
            if (x < minx || x > maxx)
            {
                if (isExt)
                    ext_Type = "none";
                return new Vector2[] { };
            }
            if (isExt)
                ext_Type = "cross";
            if (MathUtil.NumEQ(p1.Y, p2.Y))
            {//线垂直X
                return new Vector2[] { new Vector2(x, p1.Y) };
            }

            var y = (p2.Y - p1.Y) / (p2.X - p1.X) * (x - p1.X) + p1.Y;
            return new Vector2[] { new Vector2(x, y) };
        }

        /// <summary>
        /// 线段与x垂直线相交
        /// </summary>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <param name="x"></param>
        /// <returns></returns>
        public static Vector2[] LineIntersectX2D(Vector2 p1, Vector2 p2, double x)
        {
            string ext_Type;
            return LineIntersectX2D(p1, p2, x, out ext_Type, false);
        }


        /// <summary>
        /// 两根宽线相交
        ///线线相交
        /// </summary>
        /// <param name="p1"></param>
        /// <param name="dir1"></param>
        /// <param name="width1"></param>
        /// <param name="p2"></param>
        /// <param name="dir2"></param>
        /// <param name="widith2"></param>
        /// <returns></returns>
        public static Vector2[] LineWIntersectLineW2D(Vector2 p1, Vector2 dir1, double width1, Vector2 p2, Vector2 dir2, double widith2)
        {
            if (dir1.Clone().Cross(dir2) == 0) return null;//共线
            double hw1 = width1 / 2, hw2 = widith2 / 2;
            var zero = new Vector2();
            var vdir1 = dir1.Clone().RotateAround(zero, (double)(Math.PI / 2)).Normalize();//逆时针转90度
            var vdir2 = dir2.Clone().RotateAround(zero, (double)(Math.PI / 2)).Normalize();//逆时针转90度
            var ofs_p1_1 = p1.Clone().Add(vdir1.Clone().MultiplyScalar(hw1));
            var ofs_p1_2 = p1.Clone().Add(vdir1.Clone().MultiplyScalar(-hw1));
            var ofs_p2_1 = p2.Clone().Add(vdir2.Clone().MultiplyScalar(hw2));
            var ofs_p2_2 = p2.Clone().Add(vdir2.Clone().MultiplyScalar(-hw2));

            var inter_1_1 = LineIntersectLine2D(ofs_p1_1, dir1, ofs_p2_1, dir2);
            var inter_1_2 = LineIntersectLine2D(ofs_p1_1, dir1, ofs_p2_2, dir2);
            var inter_2_1 = LineIntersectLine2D(ofs_p1_2, dir1, ofs_p2_1, dir2);
            var inter_2_2 = LineIntersectLine2D(ofs_p1_2, dir1, ofs_p2_2, dir2);
            return new Vector2[] { inter_1_1, inter_1_2, inter_2_1, inter_2_2 };
        }

    }
    
    public class ClipBox
    {
        private Group SectionGroup { get; set; }
        private Group SectionFaceGroup { get; set; }
        public readonly int UnSelectChannel;
        public ClipBox(Group sectionGroup, int unSelectChanel)
        {
            this.UnSelectChannel = unSelectChanel;
            this.SectionGroup = sectionGroup;
            this.SectionGroup.removeAll();
            this.SectionFaceGroup = new Group();
            this.SectionGroup.add(this.SectionFaceGroup);
            var box = this.Box;
            this.BoxHelper = new Box3Helper(box, 0x0000FF);
            this.BoxHelper.layers.set(unSelectChanel);
            this.SectionGroup.add(BoxHelper);
            var center = box.GetCenter();
            var size = box.GetSize();
            var pxPlane = new ClipPlane().Set(new Vector3(box.Min.X, center.Y, center.Z), new Vector3(1, 0, 0));
            var nxPlane = new ClipPlane().Set(new Vector3(box.Max.X, center.Y, center.Z), new Vector3(-1, 0, 0));
            var pyPlane = new ClipPlane().Set(new Vector3(center.X, box.Min.Y, center.Z), new Vector3(0, 1, 0));
            var nyPlane = new ClipPlane().Set(new Vector3(center.X, box.Max.Y, center.Z), new Vector3(0, -1, 0));
            var pzPlane = new ClipPlane().Set(new Vector3(center.X, center.Y, box.Min.Z), new Vector3(0, 0, 1));
            var nzPlane = new ClipPlane().Set(new Vector3(center.X, center.Y, box.Max.Z), new Vector3(0, 0, -1));
            ClipPlanes.Clear();
            ClipPlanes.Push(pxPlane, nxPlane, pyPlane, nyPlane, pzPlane, nzPlane);
            ClipPlanes.ForEach(cp => cp.MoveAfter += () =>
            {
                box.SetFromPoints(ClipPlanes.Select(c => c.ControlPoint.position).ToArray());
                Set(box);
            });
            this.SectionGroup.add(ClipPlanes.Select(cp => cp.ControlPoint).ToArray());
        }
        public Box3 Box = new Box3();
        public Box3Helper BoxHelper = null;
        public ListEx<ClipPlane> ClipPlanes = new ListEx<ClipPlane>();
        public ClipBox Set(Box3 box, ListEx<Plane> clipPlaneArr = null)
        {
            this.Box.Copy(box);
            var center = box.GetCenter();
            var size = box.GetSize();
            ClipPlanes[0].Set(new Vector3(box.Min.X, center.Y, center.Z), new Vector3(1, 0, 0));
            ClipPlanes[1].Set(new Vector3(box.Max.X, center.Y, center.Z), new Vector3(-1, 0, 0));
            ClipPlanes[2].Set(new Vector3(center.X, box.Min.Y, center.Z), new Vector3(0, 1, 0));
            ClipPlanes[3].Set(new Vector3(center.X, box.Max.Y, center.Z), new Vector3(0, -1, 0));
            ClipPlanes[4].Set(new Vector3(center.X, center.Y, box.Min.Z), new Vector3(0, 0, 1));
            ClipPlanes[5].Set(new Vector3(center.X, center.Y, box.Max.Z), new Vector3(0, 0, -1));
            if (clipPlaneArr != null)
            {
                clipPlaneArr.Clear();
                clipPlaneArr.Push(ClipPlanes.Select(c => c.Plane).ToArray());
                SectionMat.clippingPlanes = clipPlaneArr;
            }
            return this;
        }
        private Material SectionMat = new MeshPhongMaterial{ color = new Color(0xFFFF00) };
        public void ClipModel(Object3D obj, bool isStackTop = false)
        {
            if (isStackTop)
            {
                this.SectionFaceGroup.children.ForEach(c => c.dispose());
                this.SectionFaceGroup.dispose();
            }
            if (obj is Group)
            {
                for (int i = 0; i < obj.children.Length; i++)
                {
                    ClipModel(obj.children[i]);
                }
            }
            else if (obj is Mesh)
            {
                var mesh = (Mesh)obj;
                for (int i = 0; i < this.ClipPlanes.Length; i++)
                {
                    var shapes = GeoUtil.GetSectionMeshShapes(this.ClipPlanes[i].Plane, mesh);
                    if (shapes != null)
                        for (int j = 0; j < shapes.Length; j++)
                        {
                            var shape3 = shapes[j];
                            var shapeGeo = new ShapeGeometry(shape3.shape);
                            shapeGeo.translateQuick(0, 0, -0.1);
                            shapeGeo.applyMatrix4(shape3.coordMatrix);
                            var secFace = new Mesh(shapeGeo, SectionMat);
                            secFace.layers.set(UnSelectChannel);
                            this.SectionFaceGroup.add(secFace);
                        }
                }
            }
        }
    }
    public class ClipPlane
    {
        public static Material PointMat = new MeshBasicMaterial{ color = new Color(0x0000FF) };
        public static Material PointMatSel = new MeshBasicMaterial{ color = new Color(0xFFFF00) };
        public Plane Plane { get; set; } = new Plane();
        public Mesh ControlPoint { get; set; } = new Mesh(new SphereGeometry(100), PointMat);
        public const int ControlPointLayerChanel = 3;
        public ClipPlane()
        {
            ControlPoint.ext["ClipPlane"] = this;
            ControlPoint.layers.set(ControlPointLayerChanel);
        }
        public ClipPlane Set(Vector3 pos, Vector3 dir)
        {
            Plane.SetFromNormalAndCoplanarPoint(dir, pos);
            ControlPoint.position.Copy(pos);
            return this;
        }

        public event Action MoveAfter;
        private ClipPlane Translate(Vector3 offset)
        {
            this.Plane.Translate(offset);
            this.ControlPoint.position.Add(offset);
            return this;
        }
        public void Drag()
        {
            this.ControlPoint.material = PointMatSel;
        }
        public void UnDrag()
        {
            this.ControlPoint.material = PointMat;
        }
        public void Move(Camera camera, Vector2 coord)
        {
            var cp = this.ControlPoint.position.Clone().Project(camera);
            cp.Z = 0;
            cp.Unproject(camera);
            var dir = this.Plane.Normal.Clone().Negate();
            var cp1 = this.ControlPoint.position.Clone().Add(dir).Project(camera);
            cp1.Z = 0;
            cp1.Unproject(camera);
            var planeNormal = cp1.Sub(cp).Normalize();
            var cp2 = new Vector3(coord);
            cp2.Unproject(camera);
            var offsetLen = cp2.Clone().Sub(cp).Dot(planeNormal);
            var offset = new Vector3().AddScaledVector(dir, offsetLen);
            Translate(offset);
            MoveAfter?.Invoke();
        }
    }
}
